<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8">
    <title>Sucha's Blog - Archive for December, 2019</title>
    <meta name="generator" content="MarkdownProjectCompositor.lua">
    <meta name="author" content="Sucha">
    <meta name="keywords" content="suchang, programming, Linux, Lua">
    <meta name="description" content="Sucha's blog">
    <link rel="shortcut icon" href="../images/ico.png">
    <link rel="stylesheet" type="text/css" href="../styles/blog.css">
    <link rel="stylesheet" type="text/css" href="../styles/prism.min.css">
    <style id="site_theme"></style>
  </head>
  <body>
    <div id="body">
      <div id="text">
	   <!-- Page published by cmark-gfm begins here --><h1>Sucha's Blog ~ Archive for December, 2019</h1>
<p><a id="p1"></a></p>
<div class="date">19年12月28日 周六 22:09</div>
<h2>LWTheme theme/skin manager for iOS8+</h2>
<p><a href="https://github.com/lalawue/LWTheme">LWTheme</a> 是一个支持 iOS8+ 的主题管理库，目前支持诸如以下 UI 元素的属性如颜色、字体随着主题替换而动态更改：</p>
<ul>
<li>UIView 背景色</li>
<li>CALayer border、shadow CGColor</li>
<li>CAGradientLayer 开始、结束颜色</li>
<li>UILabel 字体 family:size、attributed string 前/背景色</li>
<li>UIImageView imageNamed、tint image color</li>
<li>UIButton title color、font family:size、image/backgroundImage tingColor</li>
</ul>
<p>其他的 UI 元素也可以通过统一的底层接口，以及相关工具类，很容易为其添加相关主题管理属性，为已有的设置项增加换肤功能（其实就其底层提供的数据存储、通知能力来说，完全可以扩展到其他方面，不仅仅是换肤啦）。</p>
<h3>起因</h3>
<p>这个想法，起始于一个支持 iOS13 的 Dark mode 工作中，使用到的一款换肤库真的很不好用，需要花一些时间学习如何为不同的主题属性做设置，注意这里的主题属性居然可以有多个设置入口，另外虽然通过 plist 管理但实际使用时却需要通过一个中间工具来映射 key。一开始使用时，只是有点绕脑，后面觉得其接口设计，以及底层存储通知也许可以用更直接的方式。只是相关工作已经接近尾声，而我还需要更多的时间来实验。</p>
<p>实验的结果，是输出了这个主题管理库，另外为了学习 Swift，专门弄了个 Swift 版本，只不过纯 Swift 其实不支持在 extend 中动态添加存储属性，最后还是得用 @objc 来处理。</p>
<p>上面说到的这款换肤库，也许是 <a href="https://github.com/wxxsw/SwiftTheme">SwiftTheme</a> 的 ObjC 版本。</p>
<h3>接口改进</h3>
<p>说下 <a href="https://github.com/lalawue/LWTheme">LWTheme</a> 的接口改进：：</p>
<p>设置颜色等主题属性，其实是设置一个不同主题下的映射 key，比如</p>
<ul>
<li>设置主题背景色，接口描述是 UIView.lw_backgroundColor = @&quot;ViewBackgroundThemeColorKey&quot;</li>
<li>设置主题图片名，接口描述是 UIImageView.lw_imageNamed:@&quot;ImageViewThemeImageNamedKey&quot;</li>
<li>设置主题字体，接口描述是 UILabel.lw_font = @&quot;LabelThemeFontKey&quot;</li>
<li>设置 UIButton 的 image tint color ，接口描述是 UIButton lw_setImage:image tintColor:@&quot;ButtonThemeTintColorKey&quot; forState:UIControlStateNormal</li>
<li>设置 Attributed String 的前/背景色，为相关的 NSAttributedStringKey 配置颜色 key 就好，是类似的</li>
</ul>
<p>而换主题，或者说换肤时，接口是</p>
<pre><code class="language-source">LWThemeManager.sharedInstance useMode:LWThemeMode_Light withThemeMap:(NSDictionary *)themeMap
</code></pre>
<p>仅仅提供一个设置 NSDictionary 的接口就够了，用户可以自己添加更多的 mode 来区分不同的主题，只是用于业务区分而已，一旦更新 themeMap，就会触发主题替换通知，跟 mode 没有关系。</p>
<p>仅仅提供设置 NSDictionary 的接口，是因为读取 plist 或者网络数据、或者其他格式序列化数据，比如 KeyArchived 不是这个库的功能，它只管换肤，至于数据来源，跟它一点关系都没有，就应该剥离。</p>
<h3>主题字典定义</h3>
<p>各个主题下具体对应的背景色、图片、字体描述，dictionary 记录的格式，或者说内容，大概是这样：</p>
<p><strong>Light Mode</strong></p>
<pre><code class="language-source">{
    @&quot;Color&quot; : {
                    @&quot;ViewBackgroundThemeColorKey&quot; : @&quot;#FF0000&quot;,
                    @&quot;ButtonThemeTintColorKey&quot; : @&quot;FF0000&quot;.
    },
    @&quot;Image&quot; : {
                    @&quot;ImageViewThemeImageNamedKey&quot; : &quot;light_image.png&quot;,
    },
    @&quot;Font&quot; : {
                    @&quot;LabelThemeFontKey&quot; : @&quot;_:22&quot;,
    }
}
</code></pre>
<p><strong>Dark Mode</strong></p>
<pre><code class="language-source">{
    @&quot;Color&quot; : {
                   @&quot;ViewBackgroundThemeColorKey&quot; : @&quot;#00FF00&quot;,
                   @&quot;ButtonThemeTintColorKey&quot; : @&quot;0000FF&quot;.
    },
    @&quot;Image&quot; : {
                   @&quot;ImageViewThemeImageNamedKey&quot; : &quot;dark_image.png&quot;,
    },
    @&quot;Font&quot; : {
                   @&quot;LabelThemeFontKey&quot; : @&quot;bold:22&quot;,
    }
}
</code></pre>
<p>会区分颜色、图片名、字体在不同的子 dictionary 下，另外字体的定义是 family:size，下划线表示用 regular，bold、italic、以及其他的font family，需要指定。</p>
<h3>底层存储、通知实现</h3>
<p>比如设置 lw_backgroundColor，通过 colorKey 拿到实际的 UIColor 后，会设置到 UIVIew.backgroundColor 中，然后调用下面这个底层接口：</p>
<pre><code class="language-objc">- (void)lw_storeValue:(id)value forSetter:(SEL)setter;
</code></pre>
<p>这个接口描述了当主题变化时，会通过 setter 回调，传递 value，一个主题属性对应一个 setter。这样就完成了主题替换重新设置主题属性的功能。因此，实在不理解为何需要一个中间工具来映射，也许其精妙我还 get 不到吧。</p>
<p>另外，对于 UIButton 这样，设置字体颜色及 state 配对的属性，value 的的取值有考究，比如可以是一个以 state 为 key 的 Dictionary；而诸如设置 UIButton 的 image tint color 接口，会复杂一些，考虑到 color 可以有 alpha channel，这里需要区分 state 保存原来的 image，colorKey，当主题替换时，需要取当前 colorKey 对应的 UIColor 来 tint 原来的 image，避免当前 image 被带 alpha 的 UIColor 污染后，无法复原。</p>
<p>下面这个接口可以获取 UI 元素存储的主题属性：</p>
<pre><code class="language-objc">- (id)lw_valueForSetter:(SEL)setter;
</code></pre>
<h3>核心及扩展</h3>
<p>可以看到，库的核心是底层的存储、通知实现，这样就支持了 UI 元素为不同的主题属性，仅仅暴露了一个配置主题命名 key 的接口。这两个部分，在主题替换这个前提下，是可以不变的。</p>
<p>变化的部分是，为不同的 UI 元素，或者同一个 UI 元素，添加主题属性。比如把 CALayer 的 cornerRadius 作为主题属性，当主题变化时，配置不同的 cornerRadius，当然容易就可以实现。</p>
<p>结语是，只在模拟器上面跑了一下，应该没啥问题吧，:)</p>
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2019-12.html#p1">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<p><a id="p0"></a></p>
<div class="date">19年12月8日 周日 12:37</div>
<h2>Syncthing</h2>
<p>早就想着找一个存储备份照片的工具，虽然有很多在云端保存图片的网站、APP，但所有的这些都有着同样的问题，就是当忘记密码后，哭都来不及。即便绑定了邮箱、手机，也只是多了一层安慰而已。典型的就是雅虎中国gg，我的好多信息都绑定在上面，如果不是某次上去看到消息，真的是要哭死。</p>
<p>还有实际上数字内容虽然提供了方便，比如手机、电脑都可以随便通过网络访问，但是呢，如果不在本地保留一个备份，总觉得这个数字内容在物理上不属于自己。举一个不恰当的例子，你没犯错，机房也有被封的风险，最后即便数据有保全，优先权也不在你这，数据求索之路应该不会很短。所以，也算是一个洁癖的痛苦吧。</p>
<p>以上的讨论不包括安全，讨论安全很危险，还有，对于大部分人来说如果安全只是类似于账号、密码这样的安慰，那就太无聊了。</p>
<p>这里介绍一个内容同步工具，我主要是用于局域网内多设备同步，只是目前设备不包括手机，我觉得就存储来说，手机不是一个很好的设备。再说我有好几台拼凑起来的旧电脑，包括 Mac、Linux、FreeBSD，用这个工具还挺方便的。</p>
<p>大概介绍一下 <a href="https://syncthing.net/">Syncthing</a> 吧，这是一个去中心化的，基于内容块（类似 BitTorrent）的数据同步、分发工具，注意去中心化跟分布式有本质的不同，后面再详细区分。</p>
<p>支持局域网、广域网的内容同步，可以 NAT 穿透。搜索、数据传输，以及客户端本地文件的管理，在这个软件中是分别用不同的子程序处理的。</p>
<p>由于是开源的 go 编写的软件，我们可以看到整个程序的结构（其实 Wiki 有介绍），甚至可以改写其底层传输协议，不过这里不涉及。</p>
<p>比如客户端发现，用专门的 discov 程序，在客户端配置一节，默认是搜索一个 default 列表的 discov 网站，实际上这里可以填写自己配置的 discov 地址，比如，我只希望发现自己配置的其他的客户端，并做同步。对了，discov 是可以配置数字证书的。</p>
<p>数据传输层，如果是局域网，客户端是可以直接传输的。但是对于一些 NAT 内部的客户端，需要其他客户端帮忙传输的，有一层叫 relay 专门用于数据传输，配置界面上也可以配置为仅仅用于 relay，就是我只传递，但是不保存在本地。</p>
<p>因为是去中心化的客户端，并基于内容块的传输，所以，当你的配置的客户端越多，同步的数据量越大，这个优势也就越大。因为类似 BT，你只在其中一台设备上加了内容，其他的设备，都会优先下载还未下载的内容块，这样，对于后续其他未下载的部分，它们会相互查询、传递。</p>
<p>说下在旧机器上配置自动启动，不管是 Linux、FreeBSD，都不算顺利，一方面是 Unix 特有的多用户权限，另外就是 SystemV、RunScript 在启动配置的不同。我之前操作过其他软件的启动，好些年后再配置，又得重头再走一次，痛苦。</p>
<p>在最后放出配置界面之前，细说一下之前提到的分布式、去中心化的不同。</p>
<p>现在所有涉及到云的都是分布式（distributed），特点是对外表现为一个整体，提供高速响应、高可靠性，但是却可以处理任何单一服务器都无法处理的大规模的请求量、数据量。因为内部需要大量的普通服务器做实际业务处理，这里就涉及到了任务调度、数据同步、可靠性保证等等一系列的问题，为了实现这些基础的能力，需要把不同数据中心、机房的大量的服务器集合起来调度，这就是分布式。分布式是为了对外表现为一个响应的统一整体，因规模变大后而选择的方案。</p>
<p>而去中心化（decentralized）则完全不同，它本质上就不是作为统一整体来响应的，而在当前的技术能力上，做不到分布式统一整体表现的高响应。在设计上，是一个自私的系统，就是只区分我和其他。在设计上，分布式把复杂度放在了系统内部，而去中心化则把复杂度放在了 agent 的交互，总觉得这种交互有很多很多的可能，好玩极了。</p>
<p>最后附录一个配置界面的图片，通过网页配置挺方便的，还可以看到进度。</p>
<img src="http://tva1.sinaimg.cn/mw1024/6f6cc1c0gy1g9pp3ayngij21c00u0apw.jpg" width=640 >
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2019-12.html#p0">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<!-- Page published by cmark-gfm ends here -->
  <div id="foot">2004-<script>var d = new
	Date();document.write(d.getFullYear())</script> &copy;
	Sucha. Powered by MarkdownProjectCompositor.
  </div>
  </div><!-- text -->
  <div id="sidebar">
  </div><!-- sidebar -->
  <script src="../js/prism.min.js" async="async"></script>
  <script src="../js/blog_sidebar.js"></script>
  </div> <!-- body -->
</body>
</html>